using System;
using System.Collections;
using System.Collections.Generic;

namespace ErkerTech.DeltaScope.Engine
{
    public class DeltaEngine : IDisposable
    {
        private int _leftLineCount, _rightLineCount, _maxLeftLine, _maxRightLine;
        private IList<BitArray> _lcsBitMatrix;
        private IList<DeltaString> _leftSide;
        private IList<DeltaString> _rightSide;
        private bool disposed;

        ~DeltaEngine()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        private void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (disposed) return;
            if (disposing)
            {
                if (_leftSide != null)
                {
                    _leftSide.Clear();
                    _leftSide = null;
                }
                if (_rightSide != null)
                {
                    _rightSide.Clear();
                    _rightSide = null;
                }
                if (_lcsBitMatrix != null)
                {
                    foreach (var bitArray in _lcsBitMatrix)
                        bitArray.Length = 0;
                    _lcsBitMatrix.Clear();
                    _lcsBitMatrix = null;
                }
            }
            disposed = true;
        }

        private void GetLongestCommonSubSequence()
        {
            // Initialization...
            _maxLeftLine = _leftSide.Count - 1;
            _maxRightLine = _rightSide.Count - 1;

            _lcsBitMatrix = new List<BitArray>(_maxLeftLine + 2);
            for (var x = 0; x < _maxLeftLine + 2; x++)
                _lcsBitMatrix.Add(new BitArray(_maxRightLine + 1));

            // Declarations
            var leftHashCode = 0;
            var currentLine = new short[_maxRightLine + 2];
            var previousLine = new short[_maxRightLine + 2];

            // Prepare LCS matrix
            for (_leftLineCount = _maxLeftLine; _leftLineCount >= 0; _leftLineCount--)
            {
                leftHashCode = _leftSide[_leftLineCount].HashCode;

                // shift the current window over to the backup array and zero out the current window...
                currentLine.CopyTo(previousLine,0);
                Array.Clear(currentLine, 0, _maxRightLine);

                for (_rightLineCount = _maxRightLine; _rightLineCount >= 0; _rightLineCount--)
                {
                    if (leftHashCode == _rightSide[_rightLineCount].HashCode)
                        currentLine[_rightLineCount] = (short) (1 + previousLine[_rightLineCount + 1]);
                    else if (previousLine[_rightLineCount] > currentLine[_rightLineCount + 1])
                    {
                        _lcsBitMatrix[_leftLineCount][_rightLineCount] = true;
                        currentLine[_rightLineCount] = previousLine[_rightLineCount];
                    }
                    else
                    {
                        _lcsBitMatrix[_leftLineCount][_rightLineCount]= false;
                        currentLine[_rightLineCount] = currentLine[_rightLineCount + 1];
                    }
                }
            }
        }
        private static DeltaBlock GetActiveBlock(IList<DeltaBlock> blocks, bool inMatchingBlock)
        {
            if (blocks.Count == 0)
                blocks.Add(inMatchingBlock ? new DeltaBlock(DeltaState.Match) : new DeltaBlock());
            else
            {
                var lnCurrentBlockIndex = blocks.Count - 1;
                if (blocks.Count > lnCurrentBlockIndex && 
                    blocks[lnCurrentBlockIndex].DiffState == (inMatchingBlock ? DeltaState.Invalid : DeltaState.Match))
                    blocks[lnCurrentBlockIndex].Commit();
                
                if (blocks.Count <= lnCurrentBlockIndex || blocks[lnCurrentBlockIndex].IsCommitted)
                    blocks.Add(inMatchingBlock ? new DeltaBlock(DeltaState.Match) : new DeltaBlock());
            }
            return blocks[blocks.Count - 1];
        }
        private List<DeltaBlock> DiffAlgorithm()
        {
            _leftLineCount = 0;
            _rightLineCount = 0;
            DeltaBlock block = null;
            var loResults = new List<DeltaBlock>();

            // Walk through the bitmatrix we created by the loop above and produces a LCS...
            while (_leftLineCount <= _maxLeftLine && _rightLineCount <= _maxRightLine)
            {
                var inMatch = _leftSide[_leftLineCount].HashCode == _rightSide[_rightLineCount].HashCode;
                block = GetActiveBlock(loResults, inMatch);
                
                var bitMatrixResult = _lcsBitMatrix[_leftLineCount][_rightLineCount];
                if (inMatch || !bitMatrixResult)
                {
                    block.Right.Add(_rightSide[_rightLineCount].Value);
                    _rightLineCount++;
                }

                if (!inMatch && !bitMatrixResult) continue;
                block.Left.Add(_leftSide[_leftLineCount].Value);
                _leftLineCount++;
            }

            //We still have unfinished business...
            if (_leftLineCount <= _maxLeftLine || _rightLineCount <= _maxRightLine)
            {
                block = GetActiveBlock(loResults, false);
                // Add any remaining left lines...
                for (var lnCnt = _leftLineCount; lnCnt <= _maxLeftLine; lnCnt++)
                    block.Left.Add(_leftSide[lnCnt].Value);

                // Add any remaining right lines...
                for (var lnCnt = _rightLineCount; lnCnt <= _maxRightLine; lnCnt++)
                    block.Right.Add(_rightSide[lnCnt].Value);
            }

            if (block != null && !block.IsCommitted) 
                block.Commit();

            // return the block array...
            return (loResults);
        }

        public List<DeltaBlock> GetDifferences(string tsLeftSide, string tsRightSide, HashSideDelegate hashAlgorithm)
        {
            if (hashAlgorithm == null) return null;

            _leftSide = hashAlgorithm(tsLeftSide);
            _rightSide = hashAlgorithm(tsRightSide);

            // Build the LCS Matrix...
            GetLongestCommonSubSequence();

            // Return the Array of DiffBlocks...
            return DiffAlgorithm();
        }
    }
}